<?php
namespace Pinenut;

/**
 * 
 * Корневой класс
 * Наследуется реализацией хранилища, которую в свою очередь наследует уже конкретная модель
 * 
 * @author Kosh
 *
 */
use Pinenut\Model2\Updater;
use Pinenut\Model2\Finder;
use Pinenut\Model2\Updaters;

abstract class Model2 extends \Pinenut\Model2\Embedded {
	
	public $collection = null;
	
	/**
	* Внедряем зависимости
	*/
	use \Pinenut\Di\Injectable;

	/**
	 * @var \Pinenut\Model2\Adapter
	 */
	protected $_adapter;
	
	/**
	 * @var \Pinenut\Model2\Adapter
	 */	
	static protected $_adapterClass;
	
	/**
	 * Имея имя класса создаём объект адаптераи устанавливаем 
	 * имя коллекции
	 * Такой способ инициализации даёт возможность снаружи 
	 * подменять адаптер (DI), для статических обращений (для типизации например) 
	 * используем методы класса, а когда пишем или читаем - используем объект 
	 * проициированный как нужно 
	 * 
	 * @return \Pinenut\Model2\Adapter
	 */
	function getAdapter() {
		if (!$this->_adapter) {
			$class = static::$_adapterClass;
			$this->_adapter = new $class($this->collection);
		}
		
		return $this->_adapter;
	}
	
	
	/**
	 * Идентификатор сущности
	 * @var string
	 */
	public $_id;
	protected $_isNew;

	/**
	 * Признак того, что текущая модель не заселена даннымии полностью
	 * @var BOOLEAN
	 */
	protected $_isPartial;
	
	function fields() {
		return array(
			'_id'=>static::ID,
		);
	}
	
	/**
	 * Конструктор модели
	 * Создаётся пустышка, с новым идентификатором или же указанным
	 * @param unknown_type $id
	 * @param bool $load
	 */
	function __construct($id = null) {
		// внедряем зависимости
		$this->di();

		// определяем коллекцию по умолчанию, если не определена явно
		if (null === $this->collection) {
			$this->collection = str_replace('\\', '.', static::className());
		}
			
		if (null === $id) {
			// новая запись, генерим идентификатор
			$this->_isNew = true;
			$class = static::$_adapterClass;
			if ($class) {
				$id = $class::generateId();
			} else {
				throw new \Exception('Не указан класс адаптера. Скорей всего забыли внедрить зависимости');
			}
			$this->populate(['_id'=>$id], false);
				
		} else {
			$this->_isNew = false;
			$this->_isPartial = true;
			$this->populate(['_id'=>$id], true);
//			$this->_id = $id;
		}


	}
	
	public function isNew($isNew = null) {
		if (null !== $isNew) {
			$this->_isNew = $isNew;
		}
		return $this->_isNew;
	}
	
	public function isPartial($isPartial = null) {
		if (null !== $isPartial) {
			$this->_isPartial = $isPartial;
		}
		return $this->_isPartial;
	}
	
	/**
	 * Сохраняем сущность
	 */
	function save() {
		
		$this->roundEmbedded('beforeSave');

		$updaters = $this->updaters();

		// группируем модификаторы по условию
		// исполняем
		foreach ($updaters as $updater) {
			/* @var $updater \Pinenut\Model2\Updater */ 
			$updater->execute();
		}
		$this->_isNew = false;
		
		// пробегаемся по всему дереву опять исполняя afterSave
		$this->roundEmbedded('afterSave');			
	}
	
	
	/** 
	 * Для обновления всей сущности используем обновляторы 
	 * из базовой реализации (вложенного документа), но внедряем 
	 * в них дополнительное условие работы определяющее сущность 
	 * с которой работаем по первичному ключу
	 *  
	 * @see \Pinenut\Model2\Embedded::updaters()
	 * @return \Pinenut\Model2\Updater[]
	 */
	protected function updaters(array $fields = array()) {
		$isNew = $this->isNew();
		// нужно добавить в условие идентификатор сущности
		$updaters = parent::updaters();
		$updaters2 = new Updaters();
		foreach ($updaters as $updater) {
			$updater->applyFieldNames();
			$hash = $updater->hash();
			
			if (!isset($updaters2[$hash])) {
				$updaters2[$hash] = $updater;
			} else {
				$updaters2[$hash]->merge($updater);
			}
			if ($isNew) {
				$updaters2[$hash]->set('_id', $this->_id);
				$updaters2[$hash]->insert = true;
			} else {
				$updaters2[$hash]->where('_id', $this->_id);
				//$updaters2[$hash]->upsert = true;
			}
			$updaters2[$hash]->setModel($this);
		}

		return $updaters2;
	}
	

	function __toString() {
		return (string)$this->_id;
	}
	
	/**
	 * Выборка сущностей этого класса, 
	 * или если задан идентификатор - одного элемента,
	 * 
	 * @param string $id
	 * @param bool $active
	 * @return \Pinenut\Model2|\Pinenut\Model2\Finder
	 */
	public static function find($id = null, $active = true) {
		$finder = new Finder(get_called_class());
		$finder->active($active);
		if (null !== $id) {
			return $finder->where('_id', $id)->one();
		}
		return $finder;
	}
	
	/**
	 * Формируем обновлятор для этой модели
	 * 
	 * @param string $id
	 * @return \Pinenut\Model2\Updater
	 */
	public static function update($id = null) {
		$updater = new Updater(get_called_class());
		if (null !== $id) {
			$updater->where('_id', $id);
		}
		return $updater;
	}
	
	/**
	 * Формируем удалятор для удаления модели
	 *
	 * @param string $id
	 * @return \Pinenut\Model2\Updater
	 */
	public static function delete($id = null) {
		$deleter = new Updater(get_called_class());
		if (null !== $id) {
			$deleter->where('_id', $id);
		}
		return $deleter->delete();
	}
	
	/**
	 * Удалятор записи с дерганьем вложеннных документов
	 */
	public function remove () {
		$this->roundEmbedded('beforeRemove');
		
		//parent::remove();
		static::delete($this->_id)->execute();
		
		$this->roundEmbedded('afterRemove');
		
	}
	
}